#pragma once

#include <stx/core/string_types.hpp>

inline namespace stx
{
[[nodiscard]] constexpr std::string_view tosv(void const* const p,
                                              Number const      size) noexcept
{
    return std::string_view(static_cast<char const*>(p), size);
}

template<IsNonVoid T>
requires EXPR(IsStringLike<T> || IsIdentical<T, fs::path> || IsEnum<T> ||
              IsIdentical<T, bool> || IsSafeBool<T> ||
              IsIdentical<T, std::nullptr_t> || IsContiguousAnsiCharRange<T>)
[[nodiscard]] constexpr auto tosv(T const& arg)
{
    if constexpr (IsStringLike<T>)
    {
        if constexpr (IsIdentical<std::decay_t<T>, char>)
        {
            return std::basic_string_view<char>(arg, strLen(arg));
        }
        else if constexpr (IsIdentical<std::decay_t<T>, wchar_t>)
        {
            return std::basic_string_view<wchar_t>(arg, strLen(arg));
        }
        else if constexpr (IsIdentical<std::decay_t<T>, char8_t>)
        {
            return std::basic_string_view<char8_t>(arg, strLen(arg));
        }
        else if constexpr (IsIdentical<std::decay_t<T>, char16_t>)
        {
            return std::basic_string_view<char16_t>(arg, strLen(arg));
        }
        else if constexpr (IsIdentical<std::decay_t<T>, char32_t>)
        {
            return std::basic_string_view<char32_t>(arg, strLen(arg));
        }

        if constexpr (IsConstructibleFromExStdStringView<T>)
        {
            if constexpr (std::constructible_from<std::basic_string_view<char>,
                                                  T const&>)
            {
                return std::basic_string_view<char>(arg);
            }
            else if constexpr (std::constructible_from<
                                   std::basic_string_view<wchar_t>,
                                   T const&>)
            {
                return std::basic_string_view<wchar_t>(arg);
            }
            else if constexpr (std::constructible_from<
                                   std::basic_string_view<char8_t>,
                                   T const&>)
            {
                return std::basic_string_view<char8_t>(arg);
            }
            else if constexpr (std::constructible_from<
                                   std::basic_string_view<char16_t>,
                                   T const&>)
            {
                return std::basic_string_view<char16_t>(arg);
            }
            else if constexpr (std::constructible_from<
                                   std::basic_string_view<char32_t>,
                                   T const&>)
            {
                return std::basic_string_view<char32_t>(arg);
            }
            else
            {
                return InvalidType{};
            }
        }
        else
        {
            return tosv(std::data(arg), std::size(arg));
        }
    }
    else if constexpr (IsIdentical<T, fs::path>)
    {
        auto const& s = arg.native();
        return tosv(s.data(), s.size());
    }
    else if constexpr (IsEnum<T>)
    {
        auto const b1 = std::cmp_greater_equal(static_cast<std::int64_t>(arg),
                                               MAGIC_ENUM_RANGE_MIN);
        auto const b2 = std::cmp_less_equal(static_cast<std::int64_t>(arg),
                                            MAGIC_ENUM_RANGE_MAX);
        if (b1 && b2)
        {
            return magic_enum::enum_name(arg);
        }

        return "K_VALUE_IS_BEYOND_MAGIC_ENUM_RANGE"sv;
    }
    else if constexpr (IsIdentical<T, bool> || IsSafeBool<T>)
    {
        return arg ? "true"sv : "false"sv;
    }
    else if constexpr (IsIdentical<T, std::nullptr_t>)
    {
        return "nullptr"sv;
    }
    else if constexpr (IsContiguousAnsiCharRange<T>)
    {
        auto const [data, size] = getMemAreaInfo(arg);
        return tosv(data, size);
    }
    else
    {
        return InvalidType{};
    }
}

[[nodiscard]] constexpr std::size_t strLen(IsStringLike auto const& str)
{
    if constexpr (IsPointer<std::decay_t<decltype(str)>>)
    {
        IF_CONSTEVAL
        {
            auto const  sz = DECAY_COPY(str);
            auto const* p  = sz;

            while (*p)
            {
                ++p;
            }

            return static_cast<std::size_t>(p - sz);
        }
        else
        {
            return tosv(str).size();
        }
    }
    else
    {
        return tosv(str).size();
    }
}

template<IsPointer Pointer_>
requires EXPR(!IsStringLike<Pointer_>)
[[nodiscard]] inline std::string tos(Pointer_ const    p,
                                     SbLowercase const sb_lowercase)
{
    auto ret = bin2hex(&p, sizeof(p), sb_lowercase);
    ranges::reverse(ret);
    return ret;
}

template<IsPointer Pointer_>
requires EXPR(!IsStringLike<Pointer_>)
[[nodiscard]] inline std::string tos(Pointer_ const p)
{
    return tos(p, SbLowercase{});
}

[[nodiscard]] inline decltype(auto)
tos(IsIntegral auto const n, NumberBase const base = NumberBase::K_10)
{
    if constexpr (IsIdentical<decltype(n), bool>)
    {
        static auto const ls_true  = "true"s;
        static auto const ls_false = "false"s;

        return n ? (ls_true) : (ls_false);
    }
    else
    {
        auto buf = std::array<char, 24>{};
        auto [p_last, ec] =
            std::to_chars(buf.data(), buf.data() + buf.size(), n, ton(base));
        if (std::errc() != ec)
        {
            auto const sys_err = std::make_error_code(ec);
            throw std::runtime_error(sys_err.message());
        }

        auto size = Number(p_last - buf.data());
        auto ret  = std::string{};
        resizeDefaultInit(ret, size);
        std::memcpy(ret.data(), buf.data(), size);
        return ret;
    }
}

[[nodiscard]] inline std::string tos(IsEnum auto const k)
{
    auto const b1 = std::cmp_greater_equal(static_cast<std::int64_t>(k),
                                           MAGIC_ENUM_RANGE_MIN);
    auto const b2 =
        std::cmp_less_equal(static_cast<std::int64_t>(k), MAGIC_ENUM_RANGE_MAX);
    if (b1 && b2)
    {
        auto const sv  = magic_enum::enum_name(k);
        auto       ret = std::string{};
        resizeDefaultInit(ret, sv.size());
        std::memcpy(ret.data(), sv.data(), sv.size());
        return ret;
    }
    else
    {
        return tos(static_cast<std::int64_t>(k));
    }
}

[[nodiscard]] inline std::string tos(IsFloatingPoint auto const n)
{
    return std::to_string(n);
}

[[nodiscard]] inline std::string tos(Number const n)
{
    if (n.isLongDouble()) [[unlikely]]
    {
        return tos(n.getLongDouble());
    }
    else if (n.isInt64())
    {
        return tos(n.getInt64());
    }

    return tos(n.getUint64());
}

[[nodiscard]] inline decltype(auto) tos(IsStringLike auto&& s)
{
    using T     = NoCvr<decltype(s)>;
    using Char_ = StringCharType<T>;

    if constexpr (IsExStdString<T>)
    {
        return FORWARD(s);
    }
    else if constexpr (IsConstructibleFromExStdStringView<T>)
    {
        auto const sv  = std::basic_string_view<Char_>(s);
        auto       ret = std::basic_string<Char_>{};
        resizeDefaultInit(ret, sv.size());
        std::memcpy(ret.data(), sv.data(), sizeof(Char_) * sv.size());
        return ret;
    }
    else if constexpr (std::constructible_from<std::string, T const&>)
    {
        return std::basic_string<Char_>(FORWARD(s));
    }
    else
    {
        return InvalidType{};
    }
}

[[nodiscard]] inline std::string tos(IsSafeNumber auto const n)
{
    return tos(n.value);
}

[[nodiscard]] inline std::string tos(IsDurationLike auto const dur)
{
    return tos(to<Milliseconds>(dur).count());
}

[[nodiscard]] inline std::string const& tos(std::nullptr_t)
{
    static auto const ls = "nullptr"s;
    return ls;
}

template<IsIdentical<fs::path> T>
[[nodiscard]] inline std::string const& tos(T const& p)
{
    return p.native();
}

template<typename T>
concept IsConvertibleToAnsiStringView = requires(T const& v)
{
    requires IsIdentical<decltype(tosv(v)), std::string_view>;
};

template<typename T>
concept IsConvertibleToAnsiString = requires(T const& v)
{
    requires IsIdentical<decltype(tos(v)), std::string> ||
        IsIdentical<decltype(tos(v)), std::string_view>;
};

template<typename T>
concept IsStringifiable =
    IsConvertibleToAnsiStringView<T> || IsConvertibleToAnsiString<T>;

constexpr std::size_t getTotalSize(IsSizedRange auto const& head,
                                   IsSizedRange auto const&... tail)
{
    return (std::size(head) + ... + std::size(tail));
}

template<IsNonVoid Head_, IsNonVoid... Tail_>
requires requires
{
    requires true && (EXPR(IsMemArea<Head_> || IsMemAreaRange<Head_>) && ... &&
                      EXPR(IsMemArea<Tail_> || IsMemAreaRange<Tail_>));
}
[[nodiscard]] constexpr auto getTotalByteSize(Head_ const& head,
                                              Tail_ const&... tail)
{
    if constexpr (IsStringLikeRange<Head_>)
    {
        constexpr auto char_size =
            sizeof(StringCharType<RangeValueType<Head_>>);
        auto total_size = 0uz;
        for (auto const& str : head)
        {
            total_size += char_size * strLen(str);
        }

        return total_size;
    }
    else if constexpr (IsMemAreaRange<Head_>)
    {
        auto total_size = 0uz;
        for (auto const& buf : head)
        {
            auto const [uu, size] = getMemAreaInfo(buf);
            total_size += size;
        }

        if constexpr (sizeof...(tail))
        {
            return total_size + getTotalByteSize(tail...);
        }
        else
        {
            return total_size;
        }
    }
    else if constexpr (IsStringLike<Head_>)
    {
        constexpr auto char_size = sizeof(StringCharType<Head_>);
        if constexpr (sizeof...(tail))
        {
            return ((char_size * strLen(head)) + ... + getTotalByteSize(tail));
        }
        else
        {
            return char_size * strLen(head);
        }
    }
    else if constexpr (IsMemArea<Head_>)
    {
        auto const [uu, size] = getMemAreaInfo(head);
        if constexpr (sizeof...(tail))
        {
            return size + getTotalByteSize(tail...);
        }
        else
        {
            return size;
        }
    }
    else
    {
        return InvalidType{};
    }
}

template<IsContiguousRange Dst_, IsMemAreaRange MemAreaRange_>
requires requires
{
    requires IsResizable<Dst_>;
    requires 1 == sizeof(RangeValueType<Dst_>);
}
[[nodiscard]] inline Dst_ mcat2(MemAreaRange_ const& mm_rng)
{
    constexpr auto value_size =
        sizeof(RangeValueType<RangeValueType<MemAreaRange_>>);

    auto const ret_size =
        ranges::accumulate(mm_rng,
                           0uz,
                           [](auto const n, auto const& rng)
                           { return n + value_size * std::size(rng); });

    auto ret = Dst_{};
    resizeDefaultInit(ret, ret_size);

#ifdef DEBUG
    if (ret_size != ret.size())
    {
        xerr("Failed to resizeDefaultInit: "sv,
             ret_size,
             _I_,
             ret.size(),
             __FILE__,
             ":"sv,
             __LINE__);
        std::exit(EXIT_FAILURE);
    }
#endif

    auto i = 0uz;
    for (auto const& rng : mm_rng)
    {
        auto n = std::size(rng) * value_size;
        std::memcpy(&ret[i], std::data(rng), n);
        i += n;
    }

#ifdef DEBUG
    if (ret_size != i)
    {
        xerr("Invalid size: "sv, ret_size, _I_, i, __FILE__, ":"sv, __LINE__);
        std::exit(EXIT_FAILURE);
    }
#endif

    return ret;
}

template<IsContiguousRange Dst_, IsNonVoid Head_, IsNonVoid... Tail_>
requires requires
{
    requires true && (EXPR(IsMemArea<Head_> || IsMemAreaRange<Head_>) && ... &&
                      EXPR(IsMemArea<Tail_> || IsMemAreaRange<Tail_>));
    requires IsResizable<Dst_>;
    requires 1 == sizeof(RangeValueType<Dst_>);
}
[[nodiscard]] inline Dst_ mcat2(Head_&& head, Tail_ const&... tail)
{
    if constexpr (IsRvRef<Head_&&> && IsIdentical<Head_, Dst_> &&
                  EXPR(0 == sizeof...(Tail_)))
    {
        return FORWARD(head);
    }

    auto const fn_mcat = [](auto const  fn_mcat1,
                            Dst_&       dst,
                            std::size_t valid_size,
                            auto const& head1,
                            auto const&... tail1)
    {
        if constexpr (IsMemAreaRange<decltype(head1)>)
        {
            for (auto const& buf : head1)
            {
                auto const [p, size] = getMemAreaInfo(buf);
                std::memcpy(dst.data() + valid_size, p, size);
                valid_size += size;
            }
        }
        else
        {
            auto const [p, size] = getMemAreaInfo(head1);
            std::memcpy(dst.data() + valid_size, p, size);
            valid_size += size;
        }

        if constexpr (sizeof...(tail1))
        {
            fn_mcat1(fn_mcat1, dst, valid_size, tail1...);
        }

#ifdef DEBUG
        if (valid_size != dst.size())
        {
            xerr("Invalid size: "sv,
                 valid_size,
                 _I_,
                 dst.size(),
                 __FILE__,
                 ":"sv,
                 __LINE__);
            std::exit(EXIT_FAILURE);
        }
#endif
    };

    auto       ret      = Dst_{};
    auto const ret_size = getTotalByteSize(head, tail...);
    resizeDefaultInit(ret, ret_size);

#ifdef DEBUG
    if (ret_size != ret.size())
    {
        xerr("Failed to resizeDefaultInit: "sv,
             ret_size,
             _I_,
             ret.size(),
             __FILE__,
             ":"sv,
             __LINE__);
        std::exit(EXIT_FAILURE);
    }
#endif

    fn_mcat(fn_mcat, ret, 0, head, tail...);
    return ret;
}

[[nodiscard]] inline Buffer mcat(IsMemAreaRange auto const& mm_rng)
{
    return mcat2<Buffer>(mm_rng);
}

template<IsNonVoid Head_, IsNonVoid... Tail_>
requires requires
{
    requires true && (EXPR(IsMemArea<Head_> || IsMemAreaRange<Head_>) && ... &&
                      EXPR(IsMemArea<Tail_> || IsMemAreaRange<Tail_>));
}
[[nodiscard]] inline Buffer mcat(Head_&& head, Tail_ const&... tail)
{
    return mcat2<Buffer>(FORWARD(head), tail...);
}

template<IsRange StringifiableRange_>
requires IsStringifiable<RangeValueType<StringifiableRange_>>
[[nodiscard]] inline std::string scat(Prefix const&         prefix,
                                      JoinSeparator const&  separator,
                                      Postfix const&        postfix,
                                      StringifiableRange_&& rng)
{
    using ValueType_ = RangeValueType<StringifiableRange_>;

    if constexpr (IsRvRef<StringifiableRange_&&> &&
                  IsIdentical<ValueType_, std::string>)
    {
        if (prefix.empty() && separator.empty() && postfix.empty() &&
            EXPR(1 == std::size(rng)))
        {
            return std::move(std::begin(rng));
        }
    }

    auto ss  = std::vector<std::string>{};
    auto svs = std::vector<std::string_view>{};

    if constexpr (IsConvertibleToAnsiStringView<ValueType_>)
    {
        svs.reserve(std::size(rng) * 2 + 1);
        svs.emplace_back(prefix);

        bool last_item_is_separator = false;
        for (auto const& item : rng)
        {
            svs.emplace_back(tosv(item));
            svs.emplace_back(separator);
            last_item_is_separator = true;
        }

        if (last_item_is_separator)
        {
            svs.back() = postfix;
        }
        else
        {
            svs.emplace_back(postfix);
        }

        return mcat2<std::string>(svs);
    }
    else
    {
        ss.reserve(std::size(rng));
        svs.reserve(std::size(rng) * 2 + 1);

        svs.emplace_back(prefix);

        bool last_item_is_separator = false;
        for (auto const& item : rng)
        {
            ss.emplace_back(tos(item));

            svs.emplace_back(ss.back());
            svs.emplace_back(separator);
            last_item_is_separator = true;
        }

        if (last_item_is_separator)
        {
            svs.back() = postfix;
        }
        else
        {
            svs.emplace_back(postfix);
        }

        return mcat2<std::string>(svs);
    }
}

[[nodiscard]] inline std::string scat(Prefix const&          prefix,
                                      JoinSeparator const&   separator,
                                      Postfix const&         postfix,
                                      IsStringifiable auto&& head,
                                      IsStringifiable auto const&... tail)
{
    if (IsRvRef<decltype(head)> && EXPR(0 == sizeof...(tail)) &&
        prefix.empty() && separator.empty() && postfix.empty())
    {
        if constexpr (IsIdentical<decltype(head), std::string>)
        {
            return FORWARD(head);
        }
    }

    constexpr auto arg_cnt = sizeof...(tail) + 1;

    auto ss  = std::vector<std::string>{};
    auto svs = std::vector<std::string_view>{};

    ss.reserve(arg_cnt);
    svs.reserve(arg_cnt * 2 + 1);

    svs.emplace_back(prefix);

    auto const fn_cat =
        [&](auto const fn_cat1, auto const& head1, auto const&... tail1)
    {
        if constexpr (IsConvertibleToAnsiStringView<decltype(head1)>)
        {
            svs.emplace_back(tosv(head1));
            if constexpr (sizeof...(tail1) > 0)
            {
                svs.emplace_back(separator);
                return fn_cat1(fn_cat1, tail1...);
            }
        }
        else
        {
            ss.emplace_back(tos(head1));
            svs.emplace_back(ss.back());

            if constexpr (sizeof...(tail1) > 0)
            {
                svs.emplace_back(separator);
                return fn_cat1(fn_cat1, tail1...);
            }
        }

        svs.emplace_back(postfix);
    };

    fn_cat(fn_cat, head, tail...);
    return mcat2<std::string>(svs);
}

[[nodiscard]] inline std::string scat(JoinSeparator const& separator,
                                      auto&&... args)
{
    return scat(""_prefix, separator, ""_postfix, FORWARD(args)...);
}

template<IsRange StringifiableRange_>
requires IsStringifiable<RangeValueType<StringifiableRange_>>
[[nodiscard]] inline std::string scat(StringifiableRange_&& rng)
{
    return scat(""_prefix, ""_I, ""_postfix, FORWARD(rng));
}

template<IsNonVoid Head_, IsNonVoid... Tail_>
requires requires
{
    requires !IsIdentical<Head_, Prefix>;
    requires !IsIdentical<Head_, JoinSeparator>;
}
[[nodiscard]] inline std::string scat(Head_&& head, Tail_ const&... tail)
{
    return scat(""_prefix, ""_I, ""_postfix, FORWARD(head), tail...);
}

inline void check__(std::string_view const sv_err_msg,
                    IsBoolLike auto const& expr,
                    IsNonVoid auto const&... ctx)
{
    if (!expr)
    {
#ifdef NDEBUG
        auto const sv_tag = "CHECK FAILURE: "sv;
#else
        auto const sv_tag = "ASSERT FAILURE: "sv;
#endif
        if constexpr (sizeof...(ctx))
        {
            auto const s_err =
                scat(sv_tag,
                     sv_err_msg,
                     scat(" ["_prefix, ", "_I, "]"_postfix, ctx...));

            LOG_ERROR(s_err);
            RUNTIME_ERROR(s_err);
        }
        else
        {
            LOG_ERROR(sv_tag, sv_err_msg);
            RUNTIME_ERROR(sv_tag, sv_err_msg);
        }
    }
}

[[nodiscard]] inline bool
iCaseEqual(IsConvertibleToAnsiStringView auto const& a,
           IsConvertibleToAnsiStringView auto const& b)
{
    if constexpr (IsExStdClassicString<decltype(a)> &&
                  IsExStdClassicString<decltype(b)>)
    {
        return ranges::equal(views::c_str(a) | views::transform(::tolower),
                             views::c_str(b) | views::transform(::tolower));
    }
    else if constexpr (IsExStdClassicString<decltype(a)> &&
                       !IsExStdClassicString<decltype(b)>)
    {
        return ranges::equal(views::c_str(a) | views::transform(::tolower),
                             tosv(b) | views::transform(::tolower));
    }
    else if constexpr (!IsExStdClassicString<decltype(a)> &&
                       IsExStdClassicString<decltype(b)>)
    {
        return ranges::equal(tosv(a) | views::transform(::tolower),
                             views::c_str(b) | views::transform(::tolower));
    }
    else
    {
        static_assert(!IsExStdClassicString<decltype(a)> &&
                      !IsExStdClassicString<decltype(b)>);

        return ranges::equal(tosv(a) | views::transform(::tolower),
                             tosv(b) | views::transform(::tolower));
    }
}

DEFINE_SAFE_NUMBER(MinWidth, std::size_t, 2);
DEFINE_SAFE_NUMBER(FillChar, char, ' ');

enum class Align
{
    K_LEFT,
    K_RIGHT,
};

[[nodiscard]] std::string align(IsStringifiable auto const& v,
                                MinWidth const              min_width,
                                FillChar const              fill_char,
                                Align const opt = Align::K_RIGHT)
{
    using enum Align;

    if constexpr (IsConvertibleToAnsiStringView<decltype(v)>)
    {
        auto const sv = tosv(v);
        if (sv.size() >= min_width)
        {
            return tos(sv);
        }

        if (K_RIGHT == opt) [[likely]]
        {
            auto const left_fill_char_cnt = min_width - sv.size();

            auto ret = std::string{};
            resizeDefaultInit(ret, min_width);

            LOOP (i, left_fill_char_cnt)
            {
                ret[i] = fill_char;
            }

            std::memcpy(&ret[left_fill_char_cnt], sv.data(), sv.size());
            return ret;
        }

        auto const right_fill_char_cnt = min_width - sv.size();
        auto const sv_size             = sv.size();

        auto ret = std::string{};
        resizeDefaultInit(ret, min_width);
        std::memcpy(ret.data(), sv.data(), sv.size());

        LOOP (i, right_fill_char_cnt)
        {
            ret[sv_size + i] = fill_char;
        }

        return ret;
    }
    else
    {
        return align(tos(v), min_width, fill_char, opt);
    }
}

struct CiLess final
{
    using is_transparent = void;

    [[nodiscard]] bool operator()(std::string_view const sv1,
                                  std::string_view const sv2) const;
};

struct CiEqualTo final
{
    using is_transparent = void;

    [[nodiscard]] bool operator()(std::string_view const sv1,
                                  std::string_view const sv2) const;
};

struct CiHash final
{
    using is_transparent = void;

    [[nodiscard]] constexpr auto
    operator()(IsAnsiCharStringLike auto const& s) const noexcept
    {
        return std::hash<std::string>{}(s | views::transform(::tolower) |
                                        ranges::to<std::string>);
    }
};

class Lines final
{
    struct StringViewSrcContext_ final
    {
        std::string_view sv_src;
        std::size_t      cur_src_pos = {};
    };

    struct CharStreamSrcContext_ final
    {
        std::unique_ptr<std::istream> up_is;
        std::string                   s_cur;
    };

    std::variant<std::monostate, StringViewSrcContext_, CharStreamSrcContext_>
                     src_;
    std::string_view sv_cur_;

public:
    Lines() noexcept = default;
    explicit Lines(fs::path const& fp);
    explicit Lines(std::string_view const sv_src);

    NON_COPYABLE(Lines);

    class iterator final
    {
        Lines* p_owner_ = {};

    public:
        iterator() noexcept = default;
        EQUALITY_OPERATOR(iterator);

        iterator(Lines* p_owner) noexcept;

        std::string_view  operator*() const;
        std::string_view* operator->() const;

        iterator& operator++();
        iterator& operator++(int) = delete;
    };

    [[nodiscard]] iterator begin() noexcept;
    [[nodiscard]] iterator end() noexcept;

private:
    [[nodiscard]] bool moveToNext_();
};
} // namespace stx